'use strict';

const OAuth2Server = require('oauth2-server');
const userDb = require('../data/userDb');
const response = require('../response/response');

const Request = OAuth2Server.Request;
const Response = OAuth2Server.Response;

const accessToken = 3;
const authCode = 5;
const refreshToken = 30;

const secondsInMinutes = 60;
const minutesInHour = 60;
const msInSecond = 1000;
const hoursInDay = 24;

const authCodeLiftTime = authCode * secondsInMinutes * msInSecond;
const accessRefresh = hoursInDay * minutesInHour * secondsInMinutes * msInSecond;
const accessTokenLifetime = accessToken * accessRefresh;
const refreshTokenLiftTime = refreshToken * accessRefresh;

const oauth = new OAuth2Server({
    model: require('./model'),
    allowBearerTokensInQueryString: true,
    accessTokenLifetime: accessTokenLifetime,
});

function getAuthOption(token) {
    const requestOption = {
        'method': 'POST',
        'query': {
            // "response_type": "refresh_token",
        },
        'headers': {
            'Authorization': 'Bearer ' + token,
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'application/json',
            'Transfer-Encoding': 'chunked',
        },
        'body': {
            'client_id': 'clientId',
            'client_secret': 'client_secret',
            'redirect_uri': 'https://blog.ijustyce.win',
        },
    };
    return requestOption;
}

function getTokenOption(resreshToken) {
    const requestOption = {
        'method': 'POST',
        'query': {
            // "response_type": "refresh_token",
        },
        'headers': {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'application/json',
            'Transfer-Encoding': 'chunked',
        },
        'body': {
            'response_type': 'refresh_token',
            'client_id': 'client_id',
            'client_secret': 'client_secret',
            'redirect_uri': 'https://blog.ijustyce.win',
            'grant_type': 'refresh_token',
            'refresh_token': resreshToken,
        },
    };
    return requestOption;
}

function getCodeOption(clientId, clientSecret, userName, password) {
    const requestOption = {
        'method': 'POST',
        'query': {
            // "response_type": "refresh_token",
        },
        'headers': {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'application/json',
            'Transfer-Encoding': 'chunked',
        },
        'body': {
            'response_type': 'code',
            'client_id': clientId,
            'client_secret': clientSecret,
            'redirect_uri': 'https://www.ssyer.com',
            'score': 'profile',
            'state': 'xxxx',
            'grant_type': 'code',
            'userName': userName,
            'password': password,
        },
    };
    return requestOption;
}

const options = {
    authenticateHandler: {
        handle: (data) => {
            const body = data.body;
            return { userName: body.userName, password: body.password };
        },
    },
};

function authorizeHandler(req, res, options, callback) {
    const request = new Request(req);
    const responseValue = new Response(res);
    oauth.authorize(request, responseValue, options)
        .then(function(result) {
            callback(null, result);
        })
        .catch(function(err) {
            callback(err, null);
        });
}

function authenticate(req, res, event, callback) {
    const request = new Request(req);
    const responseValue = new Response(res);
    oauth.authenticate(request, responseValue)
        .then((token) => {
            callback(null, token);
        })
        .catch((err) => {
            callback(err, null);
        });
}

function token(authCode, res, event, callback) {
    const req = getTokenOption(authCode);
    const request = new Request(req);
    const responseValue = new Response(res);
    oauth.token(request, responseValue)
        .then((token) => {
            saveToken('client_id', token.refreshToken, token.accessToken, authCode, event, callback);
        })
        .catch((err) => {
            response.response(callback, event, response.get(event).RuntimeException, null);
        });
}

function checkRefreshToken(refreshToken, newRefreshToken, newAccessToken, event, callback) {
    userDb.getRefreshToken(refreshToken, (error, data) => {
        if (data == null || data.userName == null || data.date < Date.now() - refreshTokenLiftTime) {
            response.response(callback, event, response.get(event).CodeError, null);
            return;
        }
        userDb.deleteRefreshToken(refreshToken);
        userDb.saveRefreshToken(data.userName, newRefreshToken, data.appId);
        userDb.saveAccessToken(data.userName, newAccessToken, data.appId);
        const resultJson = {
            'appId': data.appId,
            'accessToken': newAccessToken,
            'refreshToken': newRefreshToken,
        };
        response.response(callback, event, null, resultJson);
    });
}

function saveToken(appId, refreshToken, accessToken, authCode, event, callback) {
    userDb.getAuthCode(authCode, appId, (error, data) => {
        if (data == null || data.userName == null || data.date < Date.now() - authCodeLiftTime) {
            checkRefreshToken(authCode, refreshToken, accessToken, event, callback);
            return;
        }
        userDb.deleteAuthCode(authCode);
        userDb.saveRefreshToken(data.userName, refreshToken, data.appId);
        userDb.saveAccessToken(data.userName, accessToken, data.appId);
        const resultJson = {
            'appId': data.appId,
            'accessToken': accessToken,
            'refreshToken': refreshToken,
        };
        response.response(callback, event, null, resultJson);
    });
}

function checkPw(result, callback, event, getToken) {
    const user = result.user;
    const authorizationCode = result.authorizationCode;
    userDb.getByNameAndPw(user.userName, user.password, (error, data) => {
        if (data == null || data.userName == null) {
            response.response(callback, event, response.get(event).UserNameMissMatch, null);
            return;
        }
        if (getToken) {
            token(authorizationCode, {}, event, callback);
            return;
        }
        delete data.password;
        authSuccess(data, authorizationCode, event, callback);
    });
}

function authSuccess(data, authorizationCode, event, callback) {
    const result = {
        'user': data,
        'authorizationCode': authorizationCode,
    };
    response.response(callback, event, null, result);
}

function authorizationCode(event, context, callback, getToken) {
    const data = event.queryStringParameters;
    if (data == null || data.userName == null || data.password == null) {
        response.response(callback, event, response.get(event).RuntimeException, null);
        return;
    }
    authorizeHandler(getCodeOption('clientId', 'clientSecret', data.userName, data.password), {}, options, (error, result) => {
        if (error != null) {
            response.response(callback, event, response.get(event).RuntimeException, null);
            return;
        }
        checkPw(result, callback, event, getToken);
    });
}

module.exports.authorizationCode = (event, context, callback) => {
    authorizationCode(event, context, callback, false);
};

module.exports.authorizationToken = (event, context, callback) => {
    authorizationCode(event, context, callback, true);
};

function checkToken(result, now) {
    if (result.user.userName == null) {
        return false;
    }
    if (result.client.id == null) {
        return false;
    }
    if (result.date == null || result.date < now - accessTokenLifetime) {
        return false;
    }
    return true;
}

module.exports.authenticate = (event, context, callback) => {
    const token = event.authorizationToken;
    if (token == null) {
        response.response(callback, event, response.get(event).AccessTokenError, null);
        return;
    }
    authenticate(getAuthOption(token), {}, event, (error, result) => {
        const now = Date.now();
        if (error != null || result == null || !checkToken(result, now)) {
            callback('Unauthorized');
            return;
        }
        if (result.date < now - accessRefresh) {
            userDb.saveAccessToken(result.user.userName, token, result.client.id);
        }
        callback(null, generatePolicy('user', 'Allow', event.methodArn, result));
    });
};

function generatePolicy(principalId, effect, resource, result) {
    const authResponse = {};

    authResponse.principalId = principalId;
    if (effect && resource) {
        const policyDocument = {};
        policyDocument.Version = '2012-10-17';
        policyDocument.Statement = [];
        const statementOne = {};
        statementOne.Action = 'execute-api:Invoke';
        statementOne.Effect = effect;
        statementOne.Resource = resource;
        policyDocument.Statement[0] = statementOne;
        authResponse.policyDocument = policyDocument;
    }
    authResponse.context = {
        'booleanKey': true,
    };
    return authResponse;
}

module.exports.test = (event, context, callback) => {
    response.response(callback, event, null, null);
};

module.exports.token = (event, context, callback) => {
    const data = event.queryStringParameters;
    if (data == null || data.authorizationCode == null) {
        response.response(callback, event, response.get(event).CodeError, null);
        return;
    }
    token(data.authorizationCode, {}, event, callback);
};

module.exports.createUser = (event, context, callback) => {
    const body = event.body;
    let data;
    try {
        data = JSON.parse(body);
    } catch (e) {
        response.response(callback, event, response.get(event).RuntimeException, null);
        return;
    }
    createUserAndLogin(data.userName, data.password, event, context, callback);
};

function createUserAndLogin(userName, password, event, context, callback) {
    userDb.createUser(userName, password, (error, result) => {
        if (error != null) {
            response.response(callback, event, response.get(event).UserExists, null);
            return;
        }
        internalLogin(event, userName, password, context, callback);
    });
}

function internalLogin(event, userName, password, context, callback) {
    event.queryStringParameters = {
        'userName': userName,
        'password': password,
    };
    authorizationCode(event, context, callback, true);
}

module.exports = {
    internalLogin: internalLogin,
    createUserAndLogin: createUserAndLogin,
};